﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using aIW;
using html_reader;
//In loving memory, of my true love
namespace mw2admintool
{
	public partial class admintool : AdminPluginBase
	{
		static basic_reader hr = new basic_reader();
		static public Queue<actionObject> actionQueue = new Queue<actionObject>();
		static String defaultSettings = "<admins><registered></registered><pending></pending>"+
			"</admins><settings><rules></rules><privmap></privmap><settings></settings></settings><aliases></aliases><badpeople_guid>"+
			"</badpeople_guid><badpeople_name></badpeople_name>", defaultLang = "<languages><use>en</use><sets><set lang=\"en\"></set></sets></languages>",
			settingsFilename = "mw2admin_settings.xml", langFilename = "mw2admin_lang.xml";
		public static Dictionary<String, userIden> idenTypeStringMap = new Dictionary<string, userIden>();
		public static Dictionary<String, String> settings = new Dictionary<string,string>(),
			aliasCmdMap = new Dictionary<string,string>();
		public static Dictionary<long, long> moutherbyguid = new Dictionary<long, long>();
		public static Dictionary<String, long> moutherbyname = new Dictionary<string, long>();
		public static Dictionary<String, String> mapAlias = new Dictionary<string, string>();
		public static Dictionary<String, Object> warnCmds = new Dictionary<string, object>();
		public static Dictionary<String, actionDef> actionMap = new Dictionary<String, actionDef>();
		public static List<String> mapList = new List<string>();
		public static List<System.Collections.IDictionary> clearableSettings =
			new List<System.Collections.IDictionary>(new System.Collections.IDictionary[]
			{
				settings,
				moutherbyguid,
				moutherbyname,
				mapAlias,
				warnCmds,
				actionMap,
				aliasCmdMap
			});
		public static Dictionary<String, String> lang = new Dictionary<string, string>();
		public static Dictionary<String, ballotType> ballotTypes = new Dictionary<string, ballotType>();
		public enum ballotType
		{
			all,
			maps,
			kicks,
			bans
		}
		public delegate commandStatus actionDel(String[] param, command cmd, ref String outstr);
		public livetranslate translator;
		private Ballot<String, long> mapBallot = new Ballot<string, long>();
		private Ballot<long, long> banBallot = new Ballot<long, long>(), kickBallot = new Ballot<long, long>();
		private adminList al = new adminList();
		private int c_votemap_max, c_votekick_max, c_voteban_max, c_votemax_num, c_itemsperpage;
		private int frame_count = 60;
		private int frame_counter = 60;
		private int prevRegPriv = 0;
		private html.html_root settingsNodeGraph, langNodeGraph;
		private bool shhMode = false;
		private int shhLevel = -1;
		public static void dictionizeEnum<TEnum>(ref Dictionary<String, TEnum> dict)
		{
			dict.Clear();
			String[] strs = Enum.GetNames(typeof(TEnum));
			foreach (String s in strs)
				dict.Add(s, (TEnum)Enum.Parse(typeof(TEnum), s));
		}
		public enum laterActions
		{
			saveSettings,
			loadSettings,
			saySomething,
			loadLang,
			saveLang
		}
		public enum userIden
		{
			guid, name, both, none
		}
		public enum commandStatus
		{
			success, parameters, error
		}
		public class informMsg
		{
			public String[] msgs;
			public long timeout, count, current;
			public informMsg(String[] msgs, long timeout)
			{
				this.msgs = msgs;
				this.timeout = timeout;
				this.count = this.timeout;
				this.current = 0;
			}
			public static List<informMsg> messages = new List<informMsg>();
			public static void addMsg(String[] msg, long timeout)
			{
				informMsg.messages.Add(new informMsg(msg, timeout));
			}
			public static void tick()
			{
				foreach (informMsg msg in informMsg.messages)
				{
					if (msg.count > 0)
						--msg.count;
					else
					{
						actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(null, loudness.loud,
							msg.msgs[msg.current++])));
						if (msg.current >= msg.msgs.Length)
							msg.current = 0;
						msg.count = msg.timeout;
					}
				}
			}
		}
		public class Ballot<TCandids, TVoters>
		{
			public Dictionary<TCandids, int> tally;
			public Dictionary<TVoters, TCandids> voters;
			public TCandids topCan = default(TCandids);
			bool retally = true, voted = false;
			public Ballot()
			{
				this.tally = new Dictionary<TCandids, int>();
				this.voters = new Dictionary<TVoters, TCandids>();
			}
			public void addCandid(TCandids can)
			{
				this.tally.Add(can, 0);
			}
			public void clearBallots()
			{
				retally = false;
				voted = false;
				voters.Clear();
				tally.Clear();
			}
			public bool Vote(TVoters voter, TCandids can)
			{
				if (this.didVote(voter))
					return false;
				voters.Add(voter, can);
				if (!this.tally.ContainsKey(can))
					tally[can] = 0;
				++tally[can];
				retally = true;
				return true;
			}
			public bool UncastVote(TVoters voter)
			{
				if (!this.voters.ContainsKey(voter))
					return false;
				if (this.tally.ContainsKey(this.voters[voter]))
					--this.tally[this.voters[voter]];
				if (this.tally[this.voters[voter]] <= 0)
					this.tally.Remove(this.voters[voter]);
				this.voters.Remove(voter);
				return true;
			}
			public bool didVote(TVoters voter)
			{
				return voters.ContainsKey(voter);
			}
			public TCandids topCandid()
			{
				int max = 0;
				this.topCan = default(TCandids);
				foreach (var can in this.tally)
					if (can.Value > max)
					{
						max = can.Value;
						this.topCan = can.Key;
						voted = true;
					}
				if (max == 0)
					voted = false;
				retally = false;
				return this.topCan;
			}
			public int topVote()
			{
				if (retally)
					this.topCandid();
				return voted ? this.tally[this.topCan] : 0;
			}
		}
		/// <summary>
		/// Do not use these members, there is no guarantee that they will be set with valid data.
		/// Do not rely on dtype, name or guid, they merely serve as a search mark for auditUser.
		/// level should always be valid but there shouldn't be any context that you should, if
		/// you are making new commands, use the supplied cmd argument. The client instance-member
		/// and the name string field act as the same property, and should be used instead of anything
		/// in this class.
		/// </summary>
		public class adminProfile
		{
			public userIden type, dtype;
			public String name, guid;
			public int level;
			public static String typeString(userIden iden)
			{
				switch (iden)
				{
					case userIden.both:
						return "both";
					case userIden.guid:
						return "guid";
					case userIden.name:
						return "name";
					case userIden.none:
						return "none";
				}
				return "";
			}
			public adminProfile(String name, String guid, int level, userIden type, userIden dtype = userIden.none)
			{
				this.name = name;
				this.type = type;
				this.dtype = dtype;
				this.guid = guid;
				this.level = level;
			}
			public override string ToString()
			{
				long j = 0;
				long.TryParse(this.guid, out j);
				return "AdminProfile(Name: " + this.name + ", GUID: " + (j == 0 ? "<Invalid GUID>" : String.Format("{0:X}", j)) + ", type: " +
					typeString(this.type) + ", level: " + this.level + ")";
			}
		}
		public class command
		{
			/// <summary>
			/// name of the command
			/// </summary>
			public String name;
			/// <summary>
			/// arguments for the command
			/// </summary>
			public String[] args;
			/// <summary>
			/// loudness of the command's echo
			/// </summary>
			public loudness sound;
			/// <summary>
			/// aiw client that issues the command
			/// </summary>
			public AdminClient client;
			/// <summary>
			/// adminprofile that issues the command, this may be null if it is a -1 user.
			/// </summary>
			public adminProfile profile;
			/// <summary>
			/// privilege level of the person that issues the command
			/// </summary>
			public int level = 0;
			/*Funny thing is, there's quote support for commands but the darn client drops all quotes in messages.*/
			public static void parse_cmdstr(ref String cmdname, ref String[] cmdargs, String msg, bool parseparams, int paramcount = 0)
			{
				Char litparchar = '\"';
				Boolean usesingquo;
				if (Boolean.TryParse(settings["allow_single_quotes"], out usesingquo))
					litparchar = usesingquo ? '\'' : '\"';
				String carg = "";
				List<String> args = new List<String>();
				int mode = 0;
				for (int i = 0; i < msg.Length; ++i)
				{
					Char c = msg[i];
					switch (mode)
					{
						case 0:
							if (c != ' ')
								carg += c;
							else
							{
								args.Add(carg);
								carg = "";
								mode = 1;
							}
							break;
						case 1:
							if (c != ' ')
							{
								if (c != litparchar)
								{
									carg += c;
									mode = 2;
								}
								else
									mode = 3;
							}
							break;
						case 2:
							if (c != ' ')
							{
								carg += c;
							}
							else
							{
								args.Add(carg);
								if (paramcount > 0)
									--paramcount;
								carg = "";
								mode = 1;
							}
							break;
						case 3:
							if (c == litparchar)
							{
								args.Add(carg);
								if (paramcount > 0)
									--paramcount;
								carg = "";
								mode = 1;
							}
							else
								carg += c;
							break;
					}
					if (!parseparams && mode == 1 && paramcount <= 0)
					{
						args.Add(msg.Substring(i+1, msg.Length-i-1));
						break;
					}
				}
				if (carg != "")
					args.Add(carg);
				if (cmdname != null) cmdname = args[0].ToLower();
				args.RemoveAt(0);
				if (cmdargs != null) cmdargs = args.ToArray();
			}
			public void parse_cmdstr(String msg, bool parseparams, int paramcount = 0)
			{
				parse_cmdstr(ref this.name, ref this.args, msg, parseparams, paramcount);
			}
			public command(adminProfile profile, loudness sound, AdminClient client)
			{
				this.sound = sound;
				this.profile = profile;
				this.level = (profile != null) ? profile.level : -1;
				this.args = new String[] { };
				this.name = "";
				this.client = client;
			}
		}
		public class actionDef
		{
			public actionDef(actionDel action, int level, String help, bool parseparams, int exparamcnt)
			{
				this.action = action;
				this.level = level;
				this.enabled = true;
				this.help = help;
				this.parseparams = parseparams;
				this.exparamcnt = exparamcnt;
			}
			public actionDel action;
			/// <summary>
			/// Level of the command
			/// </summary>
			public int level;
			/// <summary>
			/// Explicit paramater count, used with parse params
			/// </summary>
			public int exparamcnt;
			/// <summary>
			/// Is enabled
			/// </summary>
			public bool enabled;
			/// <summary>
			/// Whether or not to parse parameters. If not, exparamcnt is
			/// used to determine how many initial paramters to use and then
			/// the rest of the arguments as literals in one large argument.
			/// </summary>
			public bool parseparams;
			/// <summary>
			/// Key to admintool.lang to the localized help for this function.
			/// </summary>
			public String help;
		}
		class adminList
		{
			public List<adminProfile> admins = new List<adminProfile>(), pending = new List<adminProfile>();
			public void Clear()
			{
				this.admins.Clear();
				this.pending.Clear();
			}
			public adminProfile find(String value, userIden iden_type)
			{
				userIden iden = iden_type == userIden.both ? userIden.guid : iden_type;
				if (iden == userIden.guid)
					foreach (adminProfile admin in this.admins)
					{
						if (admin.guid == value)
							return admin;
					}
				else
					foreach (adminProfile admin in this.admins)
					{
						if (removeColors(admin.name) == value)
							return admin;
					}
				return null;
			}
			public adminProfile findPending(String value, userIden iden_type)
			{
				userIden iden = iden_type == userIden.both ? userIden.guid : iden_type;
				if (iden_type == userIden.both)
					aIW.Log.Debug("findPending is searching with an iden of both");
				if (iden == userIden.guid)
					foreach (adminProfile admin in this.pending)
					{
						if (admin.guid == value)
							return admin;
					}
				else
					foreach (adminProfile admin in this.pending)
					{
						if (admin.name == value)
							return admin;
					}
				return null;
			}
		}

		public void addAction(String name, actionDel del, String help, int level = 0, bool parseparams = true, int exparamcnt = 0)
		{
			actionMap.Add(name, new actionDef(del, level, help, parseparams, exparamcnt));
		}

		public void loadInteger(String settingname, out int input, int default_value)
		{
			if (!int.TryParse(settings[settingname], out input))
			{
				input = default_value;
				aIW.Log.Debug("Cannot load integer setting \"" + settingname + "\"");
			}
		}
		public override void OnFrame()
		{
			--this.frame_counter;
			if (this.frame_counter <= 0)
			{
				this.frame_counter = this.frame_count;
				this.onInterval();
			}
		}
		System.Threading.ReaderWriterLock actionLock = new System.Threading.ReaderWriterLock();
		public class actionObject
		{
			public laterActions action;
			public Object tag;
			public actionObject(laterActions ac, Object tag = null)
			{
				this.tag = tag;
				this.action = ac;
			}
		}
		public class sayInfo
		{
			public AdminClient who;
			public loudness sound;
			public String message;
			public sayInfo(AdminClient who, loudness sound, String message)
			{
				this.who = who;
				this.sound = sound;
				this.message = message;
			}
		}
		public void onInterval()
		{
			if (actionQueue.Count  == 0)
				return;
			try
			{
				actionObject action = actionQueue.Dequeue();
				if (action.action != laterActions.saySomething)
				{
						System.Threading.Thread work = new System.Threading.Thread(delegate(Object data)
						{
							actionObject act = data as actionObject;
							try
							{
								actionLock.AcquireWriterLock(500);
								switch(act.action)
								{
									case laterActions.saveSettings:
										saveSettings();
										break;
									case laterActions.loadSettings:
										loadSettings();
										break;
									case laterActions.loadLang:
										loadLang();
										break;
									case laterActions.saveLang:
										saveLang();
										break;
									default:
										throw new Exception("Non-thread laterAction not handled by caller thread.");
								}
								actionLock.ReleaseLock();
							}
							catch (Exception e)
							{
								aIW.Log.Debug("Interval workthread error: " + e.ToString());
							}
						});
						work.Priority = System.Threading.ThreadPriority.BelowNormal;
						work.Start(action);
						//One is good enough
						while (actionQueue.Count > 0 && actionQueue.Peek().action == action.action)
							actionQueue.Dequeue();
				}
				else
				{
					sayInfo info = action.tag as sayInfo;
					this.echo(info.who, info.sound, info.message);
					this.frame_counter = 3;
				}
			}
			catch (Exception e)
			{
				aIW.Log.Debug(e.TargetSite.Name + " Error: " + e.ToString());
			}
		}
		public enum predicateError
		{
			multiplematches,
			nomatch,
			ok
		}
		public String lastPredicateError;
		public predicateError parsePredicate(ref String pred)
		{
			String p = pred;
			if (p.Length <= 1 || p[0] != '@')
				return getPredicateError(predicateError.ok);
			p = p.Remove(0, 1);
			AdminClient[] results = this.searchClients(p);
			if (results.Length > 1)
				return getPredicateError(predicateError.multiplematches, p);
			else
				if (results.Length == 1)
				{
					pred = results[0].ClientNum.ToString();
					return getPredicateError(predicateError.ok, p);
				}
			return getPredicateError(predicateError.nomatch, p);
		}
		public predicateError getPredicateError(predicateError error, String pred = "")
		{
			if (error == predicateError.ok)
			{
				lastPredicateError = "";
				return predicateError.ok;
			}
			String s = "";
			switch (error)
			{
				case predicateError.nomatch:
					s = lang["predicate_nomatch"];
					break;
				case predicateError.multiplematches:
					s = lang["predicate_multiple"];
					break;
				default:
					s = lang["predicate_unknown"];
					break;
			}
			lastPredicateError = String.Format(lang["predicate_error"], pred, s);
			return error;
		}
		public void initSettings()
		{
			al.Clear();
			mapList.Clear();
			foreach (var dict in clearableSettings)
				dict.Clear();
			aIW.Log.Debug("Init Settings");

			initCommands();
			/*string test = ""; Code relic right here, this is what I did to rewrite code.
			foreach (KeyValuePair<String, actionDef> def in actionMap)
			{
				test += String.Format("lang.Add(\"cmd_help_{0}\", \"{1}\");\r\n", def.Key, def.Value.help);
			}
			foreach (KeyValuePair<String, actionDef> def in actionMap)
			{
				test += String.Format("addAction(\"{0}\", this.{1}, lang[\"cmd_help_{2}\"]{3}{4});\r\n", def.Key, def.Value.action.Method.Name,
					def.Key, def.Value.level == 0 && def.Value.parseparams == true ? "" : (", " + def.Value.level), def.Value.parseparams == false ? ", false" : "");
			}
			System.IO.File.WriteAllText("asdasd.txt", test);*/
			settings.Add("server_rules", "No rules are set!");
			settings.Add("maplist", "");
			settings.Add("mapchangewait", "5000");
			settings.Add("votemap_max", "3");
			settings.Add("votekick_max", "3");
			settings.Add("voteban_max", "3");
			settings.Add("votemax_num", "3");
			settings.Add("warn_cmds", "map_rotate");
			settings.Add("translate_url", @"http://translate.google.com/translate_a/t?client=t&text={0}&sl={1}&tl={2}");
			settings.Add("translate_langs", "");
			settings.Add("webrequest_timeout", "1500");
			settings.Add("items_per_page", "6");
			settings.Add("allow_single_quotes", Boolean.TrueString);
			settings.Add("emergency_cmd", "!reloadcfg settings");
			settings.Add("emergency_cmd_alias", "");
		}
		public String generateRandString()
		{
			String o = "";
			Random r = new Random();
			for (var i = 8; --i > 0; )
				o += Char.ConvertFromUtf32(r.Next(10) > 5 ? r.Next((int)'a', (int)'z') : r.Next((int)'0', (int)'9'));
			return o;
		}
		public void setupEmergencyCmd()
		{
			String r = generateRandString();
			while (aliasCmdMap.ContainsKey(r) || actionMap.ContainsKey(r))
			{
				r = generateRandString();
			}
			settings["emergency_cmd_alias"] = r.ToString();
		}
		public void loadLang()
		{
			try
			{
				initLang();
				aIW.Log.Debug("Reading language file.");
				if (!hr.readfile(langFilename))
				{
					aIW.Log.Debug("Cannot read language file! Attempting to make a new one");
					System.IO.File.WriteAllText(langFilename, defaultLang);
					if (!hr.readfile(langFilename))
						aIW.Log.Debug("Unable to read new languages file");
					else
						aIW.Log.Debug("Made new language file.");
				}
				else
					aIW.Log.Debug("Loaded language file");
				this.langNodeGraph = hr.rootGraph;

				String la = hr.query_data("languages.use[0]", html.html_node_type.text, null, true) as String;
				if (la == null || la == "")
				{
					la = "en";
					aIW.Log.Debug("No language specified in languages.use, using en by default.");
				}
				html.html_tag_cont o = hr.query_data("languages.sets", html.html_node_type.container) as html.html_tag_cont;
				html.html_tag_cont cont = null;
				if (o != null)
				{
					foreach (html.html_node n in o.content)
					{
						if (n.get_type() != html.html_node_type.container)
							continue;
						cont = n as html.html_tag_cont;
						if (cont.tag_name == "set" && (cont as html.html_tag)["lang"] == la)
							break;
					}
					if (cont == null)
						throw new Exception("Cannot find language set with lang=\"" + la + "\"! Using default");
				}
				else
					throw new Exception(hr.lastError);
				Dictionary<String, String> oldlang = new Dictionary<string, string>(lang);
				foreach (KeyValuePair<String, String> s in oldlang)
				{
					String ls = hr.query_data(s.Key + "[0]", html.html_node_type.text, cont) as String;
					if (ls == null)
					{
						aIW.Log.Debug("Language item '" + s.Key + "' does not exist.");
						continue;
					}
					lang[s.Key] = ls;
				}
			}
			catch (Exception e)
			{
				aIW.Log.Debug("Could not load lang file: " + e.ToString());
			}
		}
		public void saveLang()
		{
			aIW.Log.Debug("Saving languages");
			try
			{
				String la;
				hr.rootGraph = this.langNodeGraph;
				la = hr.query_data("languages.use[0]", html.html_node_type.text, null, true) as String;
				if (la == "")
					la = "en";
				html.html_tag_cont o = hr.query_data("languages.sets", html.html_node_type.container) as html.html_tag_cont,
					cont;
				if (o == null)
					throw new Exception(hr.lastError);
				cont = o.content.Find(delegate(html.html_node n) {
					html.html_tag c;
					if (n.get_type() != html.html_node_type.container)
						return false;
					c = n as html.html_tag;
					return c.tag_name == "set" && c["lang"] == la;
				}) as html.html_tag_cont;
				if (cont == null)
				{
					cont = new html.html_tag_cont("set", html.element_behavior.unspecified);
					cont.add_attr("lang", la);
					o.content.Add(cont);
				}
				cont.content.Clear();
				foreach (KeyValuePair<String, String> s in lang)
					cont.content.Add(new html.html_tag_cont(s.Key, s.Value, html.element_behavior.unspecified));
				if (!hr.writefile(langFilename))
					throw new Exception(hr.lastError);
				aIW.Log.Debug("Successfully written language file!");
			}
			catch (Exception e)
			{
				aIW.Log.Debug("Could not save language settings: " + e.ToString());
			}
		}
		
		public void loadWarnCommands()
		{
			String[] cmds = settings["warn_cmds"].Split(';');
			foreach (var s in cmds)
				warnCmds.Add(s, null);
		}
		public bool isMouther(aIW.AdminClient client)
		{
			return moutherbyguid.ContainsKey(client.GUID) || moutherbyname.ContainsKey(client.Name);
		}
		public String getuid(AdminClient client) { return client.Name + client.GUID; }
		public Object readData(String query, html.html_node_type type, html.html_tag origin, String error)
		{
			Object o = hr.query_data(query, type, origin, false);
			if (o == null)
			{
				aIW.Log.Debug(error + hr.lastError);
				return null;
			}
			return o;
		}

		public static String formal(String pronoun)
		{
			if (pronoun == "")
				return pronoun;
			return pronoun[0].ToString().ToUpper() + pronoun.Remove(0, 1);
		}

		void loadMapAlias()
		{
			String real = "";

			String[] aliases = settings["maplist"].Trim().Split(';'), names;
			int sz;
			foreach (String alias in aliases)
			{
				names = alias.Trim().Split(',');
				if (names.Length < 2)
				{
					aIW.Log.Debug("Only one name exists for map: " + names[0]);
					continue;
				}
				mapList.Add(names[1]);
				real = names[0].Trim();
				sz = names.Length;
				foreach (String name in names)
				{
					--sz;
					mapAlias.Add(name.Trim(), real);
				}
			}
		}

		void saveMapAlias()
		{
			//I can comment out all this code because all it really does is restructure the setting's string value
			//If I leave it alone, I have to reload the map aliases when loading the default maplist.
			Dictionary<String, List<String>> relates = new Dictionary<string,List<string>>();
			String maplist = "";
			List<String> templist;
			int sz = 0, szz;
			foreach (KeyValuePair<String, String> alias in mapAlias)
			{
				if (!relates.ContainsKey(alias.Value))
					relates.Add(alias.Value, new List<String>());
				relates[alias.Value].Add(alias.Key);
			}
			sz = relates.Count;
			foreach (KeyValuePair<String, List<String>> assoc in relates)
			{
				--sz;
				maplist += assoc.Key;
				szz = assoc.Value.Count;
				templist = assoc.Value.ToList();
				templist.RemoveAt(0);
				foreach (String alias in templist)
				{
					if (szz-- > 0) maplist += ",";
					maplist += alias;
				}
				if (sz > 0) maplist += ";";
			}
			settings["maplist"] = maplist;
		}

		public void loadRules()
		{
			html.html_tag_cont cont, o, msg;
			cont = hr.query_data("settings.rules", html.html_node_type.container) as html.html_tag_cont;
			int time = 0;
			informMsg.messages.Clear();
			if (cont != null)
			{
				foreach (html.html_node n in cont.content)
				{
					if (n.get_type() != html.html_node_type.container)
						continue;
					o = n as html.html_tag_cont;
					if (o.tag_name != "rule")
						continue;
					time = 60;
					if (!o.attr.ContainsKey("timeout"))
						aIW.Log.Debug("Rule does not specify timeout, defaulting to one minute!");
					else
						if (!int.TryParse((o as html.html_tag)["timeout"], out time))
							aIW.Log.Debug("Could not parse timeout value, defaulting to one minute!");
					List<String> templist = new List<string>();
					foreach (html.html_node m in o.content)
					{
						if (m.get_type() != html.html_node_type.container)
						{
							aIW.Log.Debug("Invalid container for rule-message!");
							continue;
						}
						msg = m as html.html_tag_cont;
						if (msg.tag_name != "message")
						{
							aIW.Log.Debug("Unknown rule-message tag '" + msg.tag_name + "'");
							continue;
						}
						String s = hr.query_data("[0]", html.html_node_type.text, msg, true) as String;
						if (s == null)
						{
							aIW.Log.Debug(hr.lastError);
							continue;
						}
						templist.Add(s);
					}
					informMsg.addMsg(templist.ToArray(), time);
				}
			}
			else
				throw new Exception(hr.lastError);
		}

		public void saveRules()
		{
			html.html_tag_cont cont;
			cont = hr.query_data("settings.rules", html.html_node_type.container) as html.html_tag_cont;
			cont.content.Clear();
			foreach (var i in informMsg.messages)
			{
				var t = new html.html_tag_cont("rule", html.element_behavior.unspecified);
				t.add_attr("timeout", i.timeout.ToString());
				foreach (String m in i.msgs)
					t.content.Add(new html.html_tag_cont("message", m, html.element_behavior.unspecified));
				cont.content.Add(t);
			}
		}

		public void loadAdmins()
		{
			html.html_tag_cont cont;
			Object o = hr.query_data("admins.registered", html.html_node_type.container);
			String per = "Unable to load admins: ";
			if (o != null)
			{
				cont = o as html.html_tag_cont;
				foreach (html.html_tag tag in cont.content)
				{
					String name, guid;
					userIden itype = userIden.name;
					int level = 0;

					o = readData("@type", html.html_node_type.none, tag, per);
					if (o == null)
						continue;
					if (!idenTypeStringMap.ContainsKey(o as String))
					{
						aIW.Log.Debug("Unknown iden type: " + o as String);
						continue;
					}
					itype = idenTypeStringMap[o as String];
					if (itype == userIden.none)
					{
						aIW.Log.Debug("Iden type is none, which is not allowed. Defaulting to guid.");
						itype = userIden.guid;
					}

					guid = "";
					if (itype == userIden.both || itype == userIden.guid)
					{
						o = readData("guid[0]", html.html_node_type.text, tag, per);
						if (o == null)
						{
							aIW.Log.Debug("Type is guid or both but no guid is specified");
							itype = userIden.name;
						}
						guid = o as String;
					}

					name = "";
					if (itype == userIden.name || itype == userIden.both)
					{
						o = readData("[0]", html.html_node_type.text, tag, per);
						if (o == null)
							continue;
						name = o as String;
					}

					o = readData("@level", html.html_node_type.none, tag, per);
					if (o == null)
						continue;
					level = int.Parse(o as String);

					if (itype == userIden.guid)
						al.admins.Add(new adminProfile("", guid, level, itype, userIden.none));
					else
						if (itype == userIden.name)
							al.admins.Add(new adminProfile(removeColors(name), "", level, itype, userIden.none));
						else
							al.admins.Add(new adminProfile(removeColors(name), guid, level, itype, userIden.none));
					aIW.Log.Debug("Added Registered: " + al.admins[al.admins.Count - 1].ToString());
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
		}

		public void loadPending()
		{
			html.html_tag_cont cont;
			Object o = hr.query_data("admins.pending", html.html_node_type.container);
			String per = "Unable to load pending: ";
			if (o != null)
			{
				cont = o as html.html_tag_cont;
				foreach (html.html_tag tag in cont.content)
				{
					if (tag.get_type() != html.html_node_type.container || tag.tag_name != "id")
						continue;
					String name, guid;
					userIden itype = userIden.name, dtype = userIden.guid;
					int level = 0;

					o = readData("@type", html.html_node_type.none, tag, per);
					if (o == null)
						continue;
					if (!idenTypeStringMap.ContainsKey(o as String))
					{
						aIW.Log.Debug("Unknown iden type: " + o as String);
						continue;
					}
					itype = idenTypeStringMap[o as String];
					if (itype == userIden.none)
					{
						aIW.Log.Debug("Iden type is none, which is not allowed. Defaulting to guid.");
						itype = userIden.guid;
					}

					guid = "";
					if (itype == userIden.both || itype == userIden.guid)
					{
						o = readData("guid", html.html_node_type.text, tag, per);
						if (o == null)//They may have put the guid in the tag itself
							o = readData("[0]", html.html_node_type.text, tag, per);
						if (o == null)
						{
							aIW.Log.Debug("Type is guid or both but no guid is specified");
							itype = userIden.name;
						}
						guid = o as String;
					}

					name = "";
					if (itype == userIden.name || itype == userIden.both)
					{
						o = readData("[0]", html.html_node_type.text, tag, per);
						if (o == null)
						{
							aIW.Log.Debug("Could not load name: " + hr.lastError);
							continue;
						}
						name = o as String;
					}

					o = readData("@level", html.html_node_type.none, tag, per);
					if (o == null)
						continue;
					level = int.Parse(o as String);

					o = readData("@typedest", html.html_node_type.none, tag, per);
					if (o == null)
						continue;
					if (!idenTypeStringMap.ContainsKey(o as String))
					{
						aIW.Log.Debug("Unknown iden type: " + o as String);
						continue;
					}
					dtype = idenTypeStringMap[o as String];
					if (itype == userIden.guid)
						al.pending.Add(new adminProfile("", name, level, itype, dtype));
					else
						if (itype == userIden.name)
							al.pending.Add(new adminProfile(name, "", level, itype, dtype));
						else
							al.pending.Add(new adminProfile(name, guid, level, itype, dtype));
					aIW.Log.Debug("Added Pending: " + al.pending[al.pending.Count - 1].ToString());
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
		}

		public void loadSettingsConstants()
		{
			Object o = hr.query_data("settings.settings", html.html_node_type.container);
			if (o != null)
			{
				Dictionary<String, String> oldsettings = new Dictionary<string, string>(settings);
				if (oldsettings.ContainsKey("emergency_cmd_alias"))
					oldsettings.Remove("emergency_cmd_alias");
				foreach (KeyValuePair<String, String> entry in oldsettings)
				{
					Object d = hr.query_data(entry.Key + "[0]", html.html_node_type.text, o as html.html_tag_cont, true);
					if (d == null)
					{
						aIW.Log.Debug("Unable to load setting \"" + entry.Key + "\": " + hr.lastError);
						continue;
					}
					settings[entry.Key] = d as String;
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
		}

		public void loadActionMap()
		{
			html.html_tag_cont cont;
			Object o = hr.query_data("settings.privmap", html.html_node_type.container);
			if (o != null)
			{
				cont = o as html.html_tag_cont;
				html.html_tag tag;
				String slev;
				foreach (KeyValuePair<String, actionDef> act in actionMap)
				{
					o = hr.query_data(act.Key, html.html_node_type.atomic, cont);
					if (o == null)
					{
						aIW.Log.Debug("Cannot parse action map \"" + act.Key + "\": " + hr.lastError);
						continue;
					}
					tag = o as html.html_tag;
					slev = tag["level"];
					if (slev == null)
					{
						aIW.Log.Debug("Cannot fetch actionmap level: " + hr.lastError);
						continue;
					}
					act.Value.level = int.Parse(slev);
					String s = hr.query_data(act.Key + ".@disabled", html.html_node_type.text, cont) as String;
					if (s != null)
						act.Value.enabled = false;
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
		}

		public void loadMouthers()
		{
			html.html_tag_cont cont;
			Object o = hr.query_data("badpeople_guid", html.html_node_type.container);
			if (o != null)
			{
				cont = o as html.html_tag_cont;
				String id, exp;
				foreach (html.html_node t in cont.content)
				{
					html.html_tag_cont tag;
					if (t.get_type() != html.html_node_type.container)
						continue;
					tag = t as html.html_tag_cont;
					if (tag.tag_name != "id")
						continue;
					id = (tag as html.html_tag)["id"];
					if (id == null)
						continue;
					exp = hr.query_data("[0]", html.html_node_type.text, tag, false) as String;
					if (exp == null)
						continue;
					moutherbyguid.Add(int.Parse(id), int.Parse(exp));
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
			o = hr.query_data("badpeople_name", html.html_node_type.container);
			if (o != null)
			{
				cont = o as html.html_tag_cont;
				String name, exp;
				foreach (html.html_node t in cont.content)
				{
					html.html_tag_cont tag;
					if (t.get_type() != html.html_node_type.container)
						continue;
					tag = t as html.html_tag_cont;
					if (tag.tag_name != "id")
						continue;
					name = (tag as html.html_tag)["id"];
					if (name == null)
						continue;
					exp = hr.query_data("[0]", html.html_node_type.text, tag, false) as String;
					if (exp == null)
						continue;
					moutherbyname.Add(name, int.Parse(exp));
				}
			}
			else
				aIW.Log.Debug(hr.lastError);
		}

		public void loadAliasCommands()
		{
			html.html_tag_cont cont = hr.query_data("aliases", html.html_node_type.container) as html.html_tag_cont;
			if (cont != null)
			{
				html.html_tag_cont tc;
				html.html_tag t;
				String name, cmd;
				foreach (html.html_node node in cont.content)
				{
					if (node.get_type() != html.html_node_type.container)
						continue;
					tc = node as html.html_tag_cont;
					t = tc as html.html_tag;
					if (tc.tag_name != "alias")
						continue;
					try
					{
						name = t["name"];
						cmd = hr.query_data("[0]", html.html_node_type.text, tc, true) as String;
						if (cmd == null)
							throw new Exception("Could not load alias command: " + hr.lastError);
						aliasCmdMap.Add(name, cmd);
					}
					catch (Exception e)
					{
						aIW.Log.Debug("Unable to load alias command: " + e.ToString());
						continue;
					}
				}
			}
			else
				aIW.Log.Debug("Unable to load alias commands: " + hr.lastError);
		}

		public void saveAliasCommands()
		{
			html.html_tag_cont cont = hr.query_data("aliases", html.html_node_type.container) as html.html_tag_cont;
			if (cont == null)
				throw new Exception("Cannot save alias commands: " + hr.lastError);
			cont.content.Clear();
			foreach (KeyValuePair<String, String> cmd in aliasCmdMap)
			{
				html.html_tag_cont c = new html.html_tag_cont("alias", cmd.Value, html.element_behavior.unspecified);
				c.add_attr("name", cmd.Key);
				cont.content.Add(c);
			}
		}

		public void loadSettings()
		{
			try
			{
				initSettings();
				aIW.Log.Debug("Reading config file.");
				if (!hr.readfile(settingsFilename))
				{
					aIW.Log.Debug("Cannot read settings file! Attempting to make a new one.");
					System.IO.File.WriteAllText(settingsFilename, defaultSettings);
					if (!hr.readfile(settingsFilename))
						aIW.Log.Debug("Unable to read new settings file");
					else
						aIW.Log.Debug("Make new settings file");
				}
				else
					aIW.Log.Debug("Loaded settings file");
				this.settingsNodeGraph = hr.rootGraph;

				//Load registered admins
				loadAdmins();
				//Load pending admins
				loadPending();
				//Load settings
				loadSettingsConstants();
				//Load map aliases
				loadMapAlias();
				//Load action map
				loadActionMap();
				//Load mouthers
				loadMouthers();
				//Load warned commands
				loadWarnCommands();
				//Load Alias commands
				loadAliasCommands();
				//Load Rules
				loadRules();

				//Load misc
				if (settings["maplist"] == "" && System.IO.File.Exists("maplist.txt"))
				{
					settings["maplist"] = System.IO.File.ReadAllText("maplist.txt");
					loadMapAlias();//Update the dictionary
					aIW.Log.Debug("Loaded default maplist: " + settings["maplist"]);
				}
				if (settings["translate_langs"] == "" && System.IO.File.Exists("langlist.txt"))
				{
					settings["translate_langs"] = System.IO.File.ReadAllText("langlist.txt");
					aIW.Log.Debug("Loaded default langlist: " + settings["translate_langs"]);
				}

				loadInteger("votemap_max", out c_votemap_max, 5);
				loadInteger("voteban_max", out c_voteban_max, 4);
				loadInteger("votekick_max", out c_votekick_max, 3);
				loadInteger("votemax_num", out c_votemax_num, 2);
				loadInteger("items_per_page", out c_itemsperpage, 6);

				if (al.admins.Count == 0)
				{
					prevRegPriv = actionMap["register"].level;
					actionMap["register"].level = -1;
				}
			}
			catch (Exception e)
			{
				aIW.Log.Debug(e.ToString());
			}
		}

		public void saveAdmins()
		{
			html.html_tag_cont o = hr.query_data("admins.registered", html.html_node_type.container) as html.html_tag_cont;
			if (o == null)
				throw new Exception(hr.lastError);
			o.content.Clear();
			foreach (adminProfile p in al.admins)
			{
				html.html_tag_cont id = new html.html_tag_cont("id", html.element_behavior.unspecified);
				id.add_attr("type", adminProfile.typeString(p.type));
				id.add_attr("level", p.level.ToString());
				if (p.type == userIden.name || p.type == userIden.both)
				{
					id.content.Add(new html.html_text(p.name));
				}
				if (p.type == userIden.guid || p.type == userIden.both)
				{
					html.html_tag_cont gc = new html.html_tag_cont("guid", html.element_behavior.unspecified);
					gc.content.Add(new html.html_text(p.guid));
					id.content.Add(gc);
				}
				o.content.Add(id);
			}
		}

		public void savePending()
		{
			html.html_tag_cont o = hr.query_data("admins.pending", html.html_node_type.container) as html.html_tag_cont;
			if (o == null)
				throw new Exception(hr.lastError);
			o.content.Clear();
			foreach (adminProfile p in al.pending)
			{
				html.html_tag_cont id = new html.html_tag_cont("id", html.element_behavior.unspecified);
				id.add_attr("type", adminProfile.typeString(p.type));
				id.add_attr("level", p.level.ToString());
				id.add_attr("typedest", adminProfile.typeString(p.dtype));
				if (p.type == userIden.name || p.type == userIden.both)
				{
					id.content.Add(new html.html_text(p.name));
				}
				if (p.type == userIden.guid || p.type == userIden.both)
				{
					html.html_tag_cont gc = new html.html_tag_cont("guid", html.element_behavior.unspecified);
					gc.content.Add(new html.html_text(p.guid));
					id.content.Add(gc);
				}
				o.content.Add(id);
			}
		}

		public void saveSettingsConstants()
		{
			html.html_tag_cont o = hr.query_data("settings.settings", html.html_node_type.container) as html.html_tag_cont;
			if (o == null)
				throw new Exception("Unable to load settings: " + hr.lastError);
			o.content.Clear();
			foreach (KeyValuePair<String, String> entry in settings)
			{
				o.content.Add(new html.html_tag_cont(entry.Key, entry.Value, html.element_behavior.unspecified));
			}
		}

		public void saveActionMap()
		{
			html.html_tag_cont o = hr.query_data("settings.privmap", html.html_node_type.container) as html.html_tag_cont;
			if (o == null)
				throw new Exception("Unable to load privmap: " + hr.lastError);
			int lastLevel = actionMap["register"].level;
			if (al.admins.Count == 0)
				actionMap["register"].level = prevRegPriv;//Store the real value
			o.content.Clear();
			foreach (KeyValuePair<String, actionDef> act in actionMap)
			{
				html.html_tag t = new html.html_tag(act.Key, html.element_behavior.unspecified);
				t.add_attr("level", act.Value.level.ToString());
				if (!act.Value.enabled)
					t.add_attr("disabled", "disabled");
				o.content.Add(t);
			}
			actionMap["register"].level = lastLevel;
		}

		public void saveMouthers()
		{
			html.html_tag_cont o = hr.query_data("badpeople_guid", html.html_node_type.container) as html.html_tag_cont;
			if (o != null)
			{
				o.content.Clear();
				foreach (KeyValuePair<long, long> p in moutherbyguid)
				{
					html.html_tag_cont t = new html.html_tag_cont("id", html.element_behavior.unspecified);
					t.attr.Add("id", p.Key.ToString());
					t.content.Add(new html.html_text(p.Value.ToString()));
					o.content.Add(t);
				}
			}
			o = hr.query_data("badpeople_name", html.html_node_type.container) as html.html_tag_cont;
			if (o != null)
			{
				o.content.Clear();
				foreach (KeyValuePair<String, long> p in moutherbyname)
				{
					html.html_tag_cont t = new html.html_tag_cont("id", html.element_behavior.unspecified);
					t.add_attr("id", p.Key);
					t.content.Add(new html.html_text(p.Value.ToString()));
					o.content.Add(t);
				}
			}
		}

		public void saveSettings()
		{
			aIW.Log.Debug("Saving settings.");
			String version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
			hr.rootGraph = this.settingsNodeGraph;
			html.html_tag o = hr.query_data("admintool", html.html_node_type.atomic) as html.html_tag;
			if (o == null)
				hr.rootGraph.content.Insert(0, o = new html.html_tag("admintool", html.element_behavior.unspecified));
			o.add_attr("version", version);
			aIW.Log.Debug("Current version: " + version);
			try
			{
				//Admins
				saveAdmins();
				//Pending
				savePending();
				//Map aliases
				saveMapAlias();
				//Settings
				saveSettingsConstants();
				//Action map
				saveActionMap();
				//Mouthers
				saveMouthers();
				//Alias commands
				saveAliasCommands();
				//Rules
				saveRules();
			}
			catch (Exception e)
			{
				aIW.Log.Debug(e.ToString());
			}
			if (!hr.writefile(settingsFilename))
				aIW.Log.Debug("Unable to write settings: " + hr.lastError);
			else
				aIW.Log.Debug("Successfully wrote settings!");
		}

		public admintool() : base()
		{
			hr.options.keep_comments = true;
			hr.options.keep_DTDs = true;
			hr.options.keep_empty_DTDs = true;
			aIW.Log.Initialize("mw2admintool.log", LogLevel.Debug, false);
			aIW.Log.Debug("=====Admin Tool=====\r\n");
			dictionizeEnum<userIden>(ref idenTypeStringMap);
			dictionizeEnum<ballotType>(ref ballotTypes);
			idenTypeStringMap.Add("xuid", userIden.guid);
			try
			{
				loadLang();
				saveLang();
				loadSettings();
				setupEmergencyCmd();
				saveSettings();
				translator = new livetranslate(settings["translate_url"], settings["translate_langs"]);
				var nt = new System.Threading.Thread(delegate()
					{
						while (true)
						{
							System.Threading.Thread.Sleep(1000);
							informMsg.tick();
						}
					});
				nt.Priority = System.Threading.ThreadPriority.Lowest;
				nt.Start();
			}
			catch (Exception e)
			{
				aIW.Log.Debug("Init Exception: " + e.ToString());
			}
		}

		public enum loudness
		{
			loud,
			silent
		}

		public adminProfile auditUser(aIW.AdminClient client)
		{
			adminProfile prof = null;
			long num;
			prof = al.find(removeColors(client.Name), userIden.name);
			if (prof == null)//If we didn't find them by name
			{
				prof = al.find(client.GUID.ToString(), userIden.guid);//See if we can find them by guid
				if (prof != null)//If we did
					if (prof.type == userIden.both)//and we were suppose to get both
						return null;//we fail, because we couldn't find the name.
			}
			else//If we did find them by name
				if (prof.type == userIden.both)//See if we were suppose to find a guid also
				{
					if (!long.TryParse(prof.guid, out num))//read the guid
					{
						aIW.Log.Debug("Unable to parse guid " + prof.ToString() + " for user " + prof.name);
						return null;
					}
					if (client.GUID != num)//If they're not the same, we fail.
						return null;
				}
			if (prof != null)
			{//Update accordingly, since commands need this information
				prof.name = client.Name;
				prof.guid = client.GUID.ToString();
			}
			return prof;
		}

		public adminProfile auditPending(aIW.AdminClient client)
		{
			adminProfile prof = null;
			long num;
			prof = al.findPending(client.Name, userIden.name);
			if (prof == null)
			{
				prof = al.findPending(client.GUID.ToString(), userIden.guid);
				if (prof != null)
					if (prof.type == userIden.both)
						return null;
			}
			else
				if (prof.type == userIden.both)
				{
					if (!long.TryParse(prof.guid, out num))
					{
						aIW.Log.Debug("Unable to parse guid for " + prof.ToString());
						return null;
					}
					if (client.GUID == num)
						return prof;
				}
			if (prof != null)
			{
				prof.name = client.Name;
				prof.guid = client.GUID.ToString();
			}
			return prof;
		}

		public adminProfile isPending(aIW.AdminClient client)
		{
			adminProfile prof = null;
			prof = al.findPending(client.Name, userIden.name);
			if (prof == null)
				prof = al.findPending(client.GUID.ToString(), userIden.guid);
			if (prof != null)
				return prof;
			return null;
		}

		public void commitChanges()
		{
			actionQueue.Enqueue(new actionObject(laterActions.saveSettings));
		}

		public void registerAdmin(adminProfile pending, aIW.AdminClient client, int level)
		{
			al.pending.Remove(pending);
			pending.type = pending.dtype;
			pending.dtype = userIden.none;
			if (pending.type == userIden.name || pending.type == userIden.both)
				pending.name = removeColors(client.Name);
			if (pending.type == userIden.guid || pending.type == userIden.both)
				pending.guid = client.GUID.ToString();
			al.admins.Add(pending);
			aIW.Log.Debug("Registered new admin " + pending.name + " by " + adminProfile.typeString(pending.type));
			commitChanges();
		}

		public bool unregisterAdmin(adminProfile prof)
		{
			if (!al.admins.Contains(prof))
				return false;
			al.admins.Remove(prof);
			if (al.admins.Count == 0)
			{
				prevRegPriv = actionMap["register"].level;
				actionMap["register"].level = -1;
			}
			return true;
		}
		public enum registerAdminStatus
		{
			registered,
			updated,
			failed
		}
		public registerAdminStatus registerAdmin(aIW.AdminClient client, int level, userIden iden)
		{
			adminProfile p = auditUser(client);
			registerAdminStatus status = registerAdminStatus.registered;
			if (level < 0)
				return registerAdminStatus.failed;
			if (p == null)
			{
				al.admins.Add(p = new adminProfile(removeColors(client.Name), client.GUID.ToString(), level, iden));
				aIW.Log.Debug("Registered new admin " + removeColors(client.Name) + " to " + p.ToString());
			}
			else
			{
				p.name = removeColors(client.Name);
				p.guid = client.GUID.ToString();
				p.level = level;
				p.type = iden;
				aIW.Log.Debug("Updated admin " + removeColors(client.Name) + " to " + p.ToString());
				status = registerAdminStatus.updated;
			}
			commitChanges();
			return status;
		}

		public void echo(AdminClient client, loudness sound, String msg)
		{
			String name = client == null ? "SYSTEM" : removeColors(client.Name);
			aIW.Log.Debug("Client Echo for \"" + name + "\" MESSAGE:" + msg);
			if (sound == loudness.loud)
				this.SayAll(lang["system_name"] + (client != null ? ("(" + name + ")") : ""), String.Format(lang["messages"], msg));
			else
				this.SayTo(client, lang["system_name"], String.Format(lang["messages"], msg));
		}

		public static bool isGranted(int src, int dest)
		{
			return dest == -1 ? true : (src == -1 ? false : src <= dest);
		}

		public AdminClient[] searchClients(String fragment)
		{
			List<AdminClient> results = new List<AdminClient>();
			List<AdminClient> clients = this.GetClients();
			fragment = Regex.Escape(fragment);//OMG thank you, just what I needed.
			aIW.Log.Debug("searchClient for \"" + fragment + "\"");
			foreach (AdminClient client in clients)
			{
				String subject = removeColors(client.Name);
				subject = removeColors(subject);
				if (Regex.IsMatch(subject, fragment, RegexOptions.IgnoreCase))
					results.Add(client);
			}
			return results.ToArray();
		}

		public static String removeColors(String str)
		{
			return Regex.Replace(str, "\\^\\d", "");
		}

		public AdminClient getClient(int id)
		{
			List<AdminClient> clients = this.GetClients();
			return clients.Find(delegate(AdminClient c) { return c.ClientNum == id; });
		}

		public AdminClient getClientByGuid(long guid)
		{
			List<AdminClient> clients = this.GetClients();
			return clients.Find(delegate(AdminClient c) { return c.GUID == guid; });
		}

		public void onMapchange()
		{
			this.mapBallot.clearBallots();
			this.kickBallot.clearBallots();
			this.banBallot.clearBallots();
		}

		public loudness stripLoud(ref String msg)
		{
			msg = msg.Trim();
			loudness sound = loudness.loud;
			if (msg[0] == '!' || msg[0] == '/')
			{
				if (msg[0] == '/')
					sound = loudness.silent;
				msg = msg.Remove(0, 1);
			}
			return sound;
		}

		override public aIW.EventEat OnSay(aIW.AdminClient client, String msg)
		{
			//aIW.Log.Debug("OnSay: " + client.Name + ": " + msg);
			command cmd;
			try
			{
				adminProfile prof;
				if (isMouther(client))
				{
					this.echo(client, loudness.silent, lang["silenced"]);
					return EventEat.EatAll;
				}

				prof = auditUser(client);
				if (prof == null)
				{
					if ((prof = auditPending(client)) != null)
					{
						registerAdmin(prof, client, prof.level);
						this.echo(client, loudness.silent, String.Format(lang["you_are_registered"], prof.level));
					}
				}

				if (shhMode)
					if ((prof == null && shhLevel == -1) || !isGranted(prof == null ? -1 : prof.level, shhLevel))
					{
						this.echo(client, loudness.silent, String.Format(shhLevel == -1 ? lang["silenced_systematic_all"] : lang["silenced_systematic"], shhLevel));
						return EventEat.EatAll;
					}

				if (msg[0] == '!' || msg[0] == '/')
				{
					
					actionDef action;
					loudness sound = stripLoud(ref msg);
					if (msg == "")
					{
						this.echo(client, sound, lang["emptycmd"]);
						return aIW.EventEat.EatAll;
					}
					cmd = new command(prof, sound, client);
					cmd.parse_cmdstr(msg, false);
					aIW.Log.Debug("User " + client.Name + " exec'd \"" + msg + "\"");
					if (aliasCmdMap.ContainsKey(cmd.name))
					{
						msg = aliasCmdMap[cmd.name];
						aIW.Log.Debug("User " + client.Name + " exec'd an alias \"" + cmd.name + "\" = \"" +
							msg + "\"");
						sound = stripLoud(ref msg);
						cmd.parse_cmdstr(msg, false);
					}
					else
						if (settings["emergency_cmd"] != "" && settings["emergency_cmd_alias"] == cmd.name)
						{
							msg = settings["emergency_cmd"];
							sound = stripLoud(ref msg);
							aIW.Log.Debug("User " + client.Name + " exec'd EMERGENCY COMMAND = \"" +
														msg + "\"");
							settings["emergency_cmd_alias"] = "";
							cmd.level = 0;
							cmd.parse_cmdstr(msg, false);
						}
					if (!actionMap.ContainsKey(cmd.name))
					{
							this.echo(client, sound, String.Format(lang["cmd_not_exist"], cmd.name));
							return aIW.EventEat.EatAll;
					}
					action = actionMap[cmd.name];
					if (!action.enabled)
					{
						this.echo(client, sound, String.Format(lang["cmd_disabled"], cmd.name));
						return aIW.EventEat.EatAll;
					}
					cmd.parse_cmdstr(msg, action.parseparams, action.exparamcnt);

					if (!isGranted(cmd.level, action.level))
					{
						this.echo(client, sound, String.Format(lang["cmd_denied"], cmd.name));
						return aIW.EventEat.EatAll;
					}
					String outstr = "";
					commandStatus status = action.action(cmd.args, cmd, ref outstr);
					if (status == commandStatus.parameters)
						outstr = String.Format(lang["insuf_params"], new Object[] {cmd.name, lang[action.help]});
					if (outstr != "")
						this.echo(cmd.client, cmd.sound, status == commandStatus.error
							? String.Format(lang["error_fmt"], outstr) : outstr);
					return EventEat.EatAll;
				}
			}
			catch (Exception e)
			{
				aIW.Log.Debug("OnSay failed: " + e.ToString());
			}
			return aIW.EventEat.EatNone;
		}

		bool isMapChanging = false;

		public bool mapExists(String map)
		{
			return mapAlias.ContainsKey(map.Trim());
		}
		public String getMapname(String map)
		{
			if (!mapExists(map))
				return "";
			return mapAlias[map.Trim()];
		}

		public void changeMap(String mapname)
		{
			this.isMapChanging = true;
			System.Threading.Thread delayEx = new System.Threading.Thread(delegate(Object data)
			{
				try
				{
					int wait = 3000, t;
					int.TryParse(settings["mapchangewait"], out wait);
					t = wait / 1000;
					while (wait > 0)
					{
						actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(null, loudness.loud, 
							String.Format(lang["map_change_countdown"], data as String, t))));
						this.frame_counter = -1;
						System.Threading.Thread.Sleep(1000);
						wait -= 1000;
						--t;
					}
					this.execCommand("map " + data as String);
					this.isMapChanging = false;
					this.onMapchange();
				}
				catch (Exception e)
				{
					aIW.Log.Debug(e.ToString());
				}
			});
			delayEx.Start(getMapname(mapname));
		}

		public void execCommand(String command, aIW.CommandRedirectDelegate del)
		{
			aIW.Log.Debug("RCON-ECHO executed \"" + command + "\"");
			this.ExecuteCommand(command + "\n", del);
		}

		public void execCommand(String command)
		{
			aIW.Log.Debug("RCON executed \"" + command + "\"");
			this.ExecuteCommand(command + "\n");
		}

		commandStatus command_Kick(String[] param, command cmd, ref String outstr)
		{
			if (param.Length == 0)
				return commandStatus.parameters;
			AdminClient client;
			String reason = "";
			int num;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out num))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			client = getClient(num);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], num);
				return commandStatus.error;
			}
			if (param.Length > 1)
				reason = param[1];
			adminProfile p = auditUser(client);
			if (p != null)
				if (!isGranted(cmd.level, p.level))
				{
					outstr = String.Format(lang["kickfail_priv"], removeColors(client.Name)); ;
					return commandStatus.error;
				}
			this.execCommand("clientkick " + num + (reason != "" ? (" \"" + reason + "\"") : ""));
			outstr = String.Format(lang["kicked"], removeColors(client.Name), reason != "" ? String.Format(lang["kickban_conj"], reason) : "");
			return commandStatus.success;
		}
		commandStatus command_Map(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			if (isMapChanging)
			{
				outstr = lang["mapfail_changing"];
				return commandStatus.error;
			}
			param[0] = param[0].Trim();
			if (!mapExists(param[0]))
			{
				outstr = String.Format(lang["map_not_exist"], param[0]);
				return commandStatus.error;
			}
			this.echo(cmd.client, loudness.loud, String.Format(lang["map_change"], formal(param[0])));
			changeMap(getMapname(param[0]));
			return commandStatus.success;
		}
		commandStatus command_Ban(String[] param, command cmd, ref String outstr)
		{
			if (param.Length == 0)
				return commandStatus.parameters;
			AdminClient client;
			String reason = "";
			int num;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out num))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			client = getClient(num);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], num);
				return commandStatus.error;
			}
			if (param.Length > 1)
				reason = param[1];
			adminProfile p = auditUser(client);
			if (p != null)
				if (!isGranted(cmd.level, p.level))
				{
					outstr = String.Format(lang["banfail_priv"], removeColors(client.Name)); ;
					return commandStatus.error;
				}
			this.execCommand("tempbanclient " + num + (reason != "" ? (" \"" + reason + "\"") : ""));
			outstr = String.Format(lang["banned"], removeColors(client.Name), reason != "" ? String.Format(lang["kickban_conj"], reason) : "");
			return commandStatus.success;
		}
		commandStatus command_Register(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			int num, level = 0;
			userIden type;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out num))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			AdminClient client = this.getClient(num);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], num);
				return commandStatus.error;
			}

			if (param.Length > 1)
			{
				if (!int.TryParse(param[1], out level) || level <= -1)
				{
					outstr = String.Format(lang["invalid_priv"], param[1]);
					return commandStatus.error;
				}
			}
			else//If they register themselves, make sure they don't accidently demote.
				level = cmd.client.GUID == client.GUID ? cmd.level : cmd.level + 1;
			

			if (param.Length > 2)
			{
				if (!idenTypeStringMap.ContainsKey(param[2]))
				{
					outstr = String.Format(lang["cant_use_identype"], param[2]);
					return commandStatus.error;
				}
				type = idenTypeStringMap[param[2]];
			}
			else
				type = userIden.both;
			if (type == userIden.none)
			{
				outstr = lang["none_identype"];
				return commandStatus.error;
			}
			if (al.admins.Count == 0)
			{
				level = 0;
				actionMap[cmd.name].level = this.prevRegPriv;
				outstr = lang["register_autoroot"];
			}
			if (level <= cmd.level && cmd.level != 0)
			{
				outstr = String.Format(lang["register_update_priv_error"], removeColors(cmd.client.Name), cmd.level);
				return commandStatus.error;
			}

			registerAdminStatus status = registerAdmin(client, level, type);
			if (status == registerAdminStatus.registered)
				outstr += String.Format(lang["register_success"], removeColors(client.Name), level);
			else
				if (status == registerAdminStatus.updated)
					outstr += String.Format(lang["register_update"], removeColors(client.Name), level);
				else
					outstr += String.Format(lang["register_error"], removeColors(client.Name));
			if (status != registerAdminStatus.failed)
				this.echo(client, loudness.silent, String.Format(lang["you_are_registered"], level));
			return commandStatus.success;
		}
		commandStatus command_findId(String[] param, command cmd, ref String outstr)
		{
			String res = "";
			if (param.Length == 0)
				return commandStatus.parameters;
			AdminClient[] clients = searchClients(param[0]);
			int num = clients.Length;
			foreach (AdminClient client in clients)
			{
				--num;
				res += removeColors(client.Name) + ": " + client.ClientNum;
				if (num > 0)
					res += "; ";
			}
			outstr = String.Format(lang["findresult"], res);
			return commandStatus.success;
		}
		commandStatus command_VoteMap(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			int cc = this.GetClients().Count();
			String realmap = "";
			if (this.isMapChanging)
			{
				outstr = lang["votemap_fail_changing"];
				return commandStatus.error;
			}
			if (cc < c_votemax_num)
			{
				outstr = String.Format(lang["votes_underrep"], c_voteban_max);
				return commandStatus.error;
			}
			if (this.mapBallot.didVote(cmd.client.GUID))
			{
				outstr = String.Format(lang["votemap_prior"], removeColors(cmd.client.Name));
				return commandStatus.error;
			}

			param[0] = param[0].ToLower().Trim();

			if (!mapExists(param[0]))
			{
				outstr = String.Format(lang["map_not_exist"], param[0]);
				return commandStatus.error;
			}
			realmap = getMapname(param[0]);
			this.mapBallot.Vote(cmd.client.GUID, realmap);
			outstr = "";
			this.echo(cmd.client, loudness.loud, String.Format(lang["votemap"], removeColors(cmd.client.Name), formal(param[0])));
			if (this.mapBallot.topVote() >= c_votemap_max || this.mapBallot.topVote() == cc)
			{
				realmap = getMapname(this.mapBallot.topCandid());
				changeMap(realmap);
				this.mapBallot.clearBallots();
				this.echo(cmd.client, loudness.loud, String.Format(lang["map_change"], realmap));
			}

			return commandStatus.success;
		}
		commandStatus command_VoteKick(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			int cc = this.GetClients().Count(), cid;
			param[0] = param[0].Trim();
			AdminClient client;
			if (cc < c_votemax_num)
			{
				outstr = String.Format(lang["votes_underrep"], c_voteban_max);
				return commandStatus.error;
			}
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out cid))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			if (this.kickBallot.didVote(cmd.client.GUID))
			{
				outstr = String.Format(lang["votekick_prior"], removeColors(cmd.client.Name));
				return commandStatus.error;
			}
			client = getClient(cid);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], cid);
				return commandStatus.error;
			}
			this.kickBallot.Vote(cmd.client.GUID, client.GUID);
			outstr = "";
			this.echo(cmd.client, loudness.loud, String.Format(lang["votekick"], removeColors(cmd.client.Name), removeColors(client.Name)));
			if (this.kickBallot.topVote() >= c_votekick_max || this.kickBallot.topVote() == cc)
			{
				AdminClient kc = getClientByGuid(this.kickBallot.topCandid());
				this.echo(cmd.client, loudness.loud, String.Format(lang["we_votekick"], removeColors(kc.Name)));
				this.execCommand("clientkick " + kc.ClientNum + " \"" + String.Format(lang["kick_reason"], this.kickBallot.topVote()) + "\"");
				this.kickBallot.clearBallots();
			}
			return commandStatus.success;
		}
		commandStatus command_VoteBan(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			int cc = this.GetClients().Count(), cid;
			param[0] = param[0].Trim();
			AdminClient client;
			if (cc < c_votemax_num)
			{
				outstr = String.Format(lang["votes_underrep"], c_voteban_max);
				return commandStatus.error;
			}
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out cid))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			if (this.banBallot.didVote(cmd.client.GUID))
			{
				outstr = String.Format(lang["voteban_prior"], removeColors(cmd.client.Name));
				return commandStatus.error;
			}
			client = getClient(cid);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], cid);
				return commandStatus.error;
			}
			this.banBallot.Vote(cmd.client.GUID, client.GUID);
			this.echo(cmd.client, loudness.loud, String.Format(lang["voteban"], removeColors(cmd.client.Name), removeColors(client.Name)));
			outstr = "";
			if (this.banBallot.topVote() >= c_voteban_max || this.banBallot.topVote() == cc)
			{
				AdminClient bc = getClientByGuid(this.banBallot.topCandid());
				this.echo(cmd.client, loudness.loud, String.Format(lang["we_voteban"], removeColors(bc.Name)));
				this.execCommand("tempbanclient " + bc.ClientNum + " \"" + String.Format(lang["ban_reason"], this.banBallot.topVote()) + "\"");
				this.banBallot.clearBallots();
			}
			return commandStatus.success;
		}
		commandStatus command_UnVoteKick(String[] param, command cmd, ref String outstr)
		{
			if (!kickBallot.didVote(cmd.client.GUID))
			{
				outstr = lang["did_not_vote_kick"];
				return commandStatus.error;
			}
			kickBallot.UncastVote(cmd.client.GUID);
			outstr = String.Format(lang["votekick_uncast"], removeColors(cmd.client.Name));
			return commandStatus.success;
		}
		commandStatus command_UnVoteBan(String[] param, command cmd, ref String outstr)
		{
			if (!banBallot.didVote(cmd.client.GUID))
			{
				outstr = lang["did_not_vote_ban"];
				return commandStatus.error;
			}
			banBallot.UncastVote(cmd.client.GUID);
			outstr = String.Format(lang["voteban_uncast"], removeColors(cmd.client.Name));
			return commandStatus.success;
		}
		commandStatus command_UnVoteMap(String[] param, command cmd, ref String outstr)
		{
			if (!mapBallot.didVote(cmd.client.GUID))
			{
				outstr = lang["did_not_vote_map"];
				return commandStatus.error;
			}
			mapBallot.UncastVote(cmd.client.GUID);
			outstr = String.Format(lang["votemap_uncast"], removeColors(cmd.client.Name));
			return commandStatus.success;
		}
		commandStatus command_RconEcho(String[] param, command cmd, ref String outstr)
		{
			if (param.Length == 0)
				return commandStatus.parameters;
			Match cname = Regex.Match(param[0], ".+");
			if (cname.Success && warnCmds.ContainsKey(cname.Value))
			{
				outstr = String.Format(lang["rconecho_warn"], cname.Value);
				return commandStatus.error;
			}
			try
			{
				outstr = String.Format(lang["rcon_sent_echo"], param[0]);
				this.execCommand(param[0], delegate(String data)
				{
					try
					{
						data = removeColors(Regex.Replace(data, "\\\"", "'"));
						this.echo(cmd.client, cmd.sound, String.Format(lang["rconecho"], data));
					}
					catch (Exception e)
					{
						aIW.Log.Debug("Rcon echo error: " + e.ToString());
					}
				});
			}
			catch (Exception e)
			{
				aIW.Log.Debug("Rcon execute error: " + e.ToString());
			}
			return commandStatus.success;
		}
		commandStatus command_Rcon(String[] param, command cmd, ref String outstr)
		{
			if (param.Length == 0)
				return commandStatus.parameters;
			outstr = String.Format(lang["rcon_sent"], param[0]);
			try
			{
				this.execCommand(param[0]);
			}
			catch (Exception e)
			{
				aIW.Log.Debug(outstr = "Rcon execute error: " + e.ToString());
			}
			return commandStatus.success;
		}
		commandStatus command_UnShutup(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			int num;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out num))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			AdminClient client = this.getClient(num);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], num);
				return commandStatus.error;
			}
			outstr = (moutherbyguid.Remove(client.GUID) || moutherbyname.Remove(client.Name)) ?
				("Unshutup'd user " + removeColors(client.Name)) : ("User " + removeColors(client.Name) + "is not on shutup list.");
			return commandStatus.success;
		}
		commandStatus command_Shutup(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			userIden type = userIden.none;
			int num = -1;
			if (param.Length > 1)
			{
				if (!idenTypeStringMap.ContainsKey(param[1]))
				{
					outstr = String.Format(lang["unknown_identype"], param[1]);
					return commandStatus.error;
				}
				type = idenTypeStringMap[param[1]];
			}
			else
				type = userIden.guid;
			if (type != userIden.guid && type != userIden.name)
			{
				outstr = String.Format(lang["cant_use_identype"], param[1]);
				return commandStatus.error;
			}
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out num))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			AdminClient client = this.getClient(num);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], num);
				return commandStatus.error;
			}
			if (type == userIden.name)
				moutherbyname.Add(client.Name, 0);
			else
				moutherbyguid.Add(client.GUID, 0);
			outstr = String.Format(lang["silence_person"], removeColors(client.Name));
			return commandStatus.success;
		}
		commandStatus command_About(String[] param, command cmd, ref String outstr)
		{
			outstr = String.Format(lang["about"],
				System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
			return commandStatus.success;
		}
		commandStatus command_ClearShutup(String[] param, command cmd, ref String outstr)
		{
			if (moutherbyguid.Count == 0 && moutherbyname.Count == 0)
			{
				outstr = lang["shutup_empty"];
				return commandStatus.success;
			}
			moutherbyguid.Clear();
			moutherbyname.Clear();
			outstr = lang["clear_shutup"];
			return commandStatus.success;
		}
		commandStatus command_UnRegister(String[] param, command cmd, ref String outstr)
		{
			if (param.Length == 0)
				return commandStatus.parameters;
			int cid;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out cid))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			AdminClient client = this.getClient(cid);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], cid);
				return commandStatus.error;
			}
			adminProfile p = auditUser(client);
			if (p == null)
			{
				outstr = String.Format(lang["user_not_admin"], removeColors(client.Name));
				return commandStatus.error;
			}
			if (p.level <= cmd.level && p.level != 0 && (p.guid != cmd.client.GUID.ToString()))
			{
				outstr = String.Format(lang["unregister_priv_error"], removeColors(client.Name));
				return commandStatus.error;
			}
			if (unregisterAdmin(p))
			{
				if (p.guid == cmd.client.GUID.ToString())
					outstr = String.Format(lang["unregister_myself"], removeColors(client.Name) );
				else
					outstr = String.Format(lang["unregister_admin"], removeColors(client.Name));
			}
			else
				outstr = String.Format(lang["unregister_error"], removeColors(client.Name));
			commitChanges();
			return commandStatus.success;
		}

		public commandStatus command_ListMaps(String[] param, command cmd, ref String outstr)
		{
			int page = 0, pages = (mapList.Count / this.c_itemsperpage) + (mapList.Count % this.c_itemsperpage == 0 ? 0 : 1);
			String maps = "";
			if (mapList.Count == 0)
			{
				outstr = lang["no_maps"];
				return commandStatus.error;
			}
			if (param.Length > 0)
				if (!int.TryParse(param[0], out page))
				{
					outstr = String.Format(lang["invalid_page"], param[0], pages);
					return commandStatus.error;
				}
			if (page > 0)
				--page;
			if (page >= pages)
			{
				outstr = String.Format(lang["invalid_page"], page + 1, pages);
				return commandStatus.error;
			}
			List<String> disp = new List<string>();
			for (int i = page * this.c_itemsperpage, c = i + this.c_itemsperpage; i < c && i < mapList.Count; ++i)
				disp.Add(mapList[i]);
			int num = disp.Count;
			foreach (String s in disp)
			{
				--num;
				maps += s;
				if (num > 0)
					maps += " ";
			}
			outstr = String.Format(lang["map_help"], page + 1, pages, maps);
			return commandStatus.success;
		}

		public commandStatus command_ListAliases(String[] param, command cmd, ref String outstr)
		{
			int page = 0, pages = (aliasCmdMap.Count / this.c_itemsperpage) + (aliasCmdMap.Count % this.c_itemsperpage == 0 ? 0 : 1);
			String aliases = "";
			if (aliasCmdMap.Count == 0)
			{
				outstr = lang["no_aliases"];
				return commandStatus.error;
			}
			if (param.Length > 0)
				if (!int.TryParse(param[0], out page))
				{
					outstr = String.Format(lang["invalid_page"], param[0], pages);
					return commandStatus.error;
				}
			if (page > 0)
				--page;
			if (page >= pages)
			{
				outstr = String.Format(lang["invalid_page"], page + 1, pages);
				return commandStatus.error;
			}
			List<String> disp = new List<string>();
			foreach (var a in aliasCmdMap)
				disp.Add(a.Key);
			disp.RemoveRange(0, this.c_itemsperpage * page);
			if (disp.Count > this.c_itemsperpage)
				disp.RemoveRange(this.c_itemsperpage, disp.Count - this.c_itemsperpage);
			int num = disp.Count;
			foreach (String a in disp)
			{
				--num;
				aliases += "!" + a;
				if (num > 0)
					aliases += " ";
			}
			outstr = String.Format(lang["alias_help"], page + 1, pages, aliases);
			return commandStatus.success;
		}

		commandStatus command_Help(String[] param, command cmd, ref String outstr)
		{
			int num = actionMap.Count;
			if (param.Length > 0 && param[0][0] == '!')
			{
				param[0] = param[0].Remove(0, 1);
				String c = param[0].ToLower();
				if (!actionMap.ContainsKey(c))
				{
					if (aliasCmdMap.ContainsKey(c))
						outstr = String.Format(lang["help_alias"], c, aliasCmdMap[c]);
					else
						outstr = String.Format(lang["cmd_not_exist"], c);
				}
				else
					if (actionMap[c].help != null && lang.ContainsKey(actionMap[c].help))
						outstr = String.Format(lang["help_cmd"], c, actionMap[c].level, lang[actionMap[c].help]);
					else
						outstr = String.Format(lang["no_help"], c);
			}
			else
			{
				int page = 0, pages = 0;
				if (param.Length > 0)
					int.TryParse(param[0], out page);
				if (page > 0) --page;
				
				List<KeyValuePair<String, actionDef>> usable = new List<KeyValuePair<String, actionDef>>();
				foreach (KeyValuePair<String, actionDef> act in actionMap)
				{
					if (!act.Value.enabled || !isGranted(cmd.level, act.Value.level))
						continue;
					usable.Add(act);
				}
				if (usable.Count == 0)
				{
					outstr = lang["no_cmds"];
					return commandStatus.error;
				}
				pages = (usable.Count / this.c_itemsperpage) + ((usable.Count % this.c_itemsperpage == 0) ? 0 : 1);
				aIW.Log.Debug("Pages div: " + (usable.Count / this.c_itemsperpage) + " mod: " + (usable.Count % this.c_itemsperpage));
				aIW.Log.Debug("Pages: " + pages + " Page: " + page);
				if (page >= pages)
				{
					outstr = String.Format(lang["invalid_page"], page + 1, pages);
					return commandStatus.error;
				}
				usable.RemoveRange(0, page * this.c_itemsperpage);
				if (usable.Count > this.c_itemsperpage)
					usable.RemoveRange(this.c_itemsperpage, usable.Count - this.c_itemsperpage);
				String cmds = "";
				num = usable.Count;
				foreach (KeyValuePair<String, actionDef> act in usable)
				{
					--num;
					cmds += "!" + act.Key;
					if (num > 0) cmds += " ";
				}
				outstr = String.Format(lang["help_list_cmds"], cmds, (page + 1), pages);
			}
			this.echo(cmd.client, loudness.silent, outstr);
			outstr = "";
			return commandStatus.success;
		}

		commandStatus command_Trans(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 2)
				return commandStatus.parameters;
			String type = param[0], msg = param[1], from = "", to = "";
			if (!translator.readConversion(type, ref from, ref to))
			{
				outstr = String.Format(lang["trans_invalid_conv"], type);
				return commandStatus.error;
			}
			try
			{
				if (translator.translate(cmd, msg, from, to, this.recvTransMsgCallback) != livetranslate.transtatus.sent)
				{
					outstr = String.Format(lang["trans_send_error"], translator.lastError);
					return commandStatus.error;
				}
			}
			catch (Exception e)
			{
				aIW.Log.Debug("commandTrans error: " + e.ToString());
			}
			return commandStatus.success;
		}

		commandStatus command_SetCmdLevel(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 2)
				return commandStatus.parameters;
			int newlevel = 0;
			String name = param[0].ToLower();
			if (!actionMap.ContainsKey(name))
			{
				outstr = String.Format(lang["cmd_unknown"], param[0]);
				return commandStatus.error;
			}
			if (!int.TryParse(param[1], out newlevel) || newlevel < -1)
			{
				outstr = String.Format(lang["cmd_invalid_level"], param[1]);
				return commandStatus.error;
			}
			actionMap[name].level = newlevel;
			outstr = String.Format(lang["cmd_set_level"], name, newlevel);
			commitChanges();
			return commandStatus.success;
		}

		commandStatus command_Disable(String[] param, command cmd, ref String outstr)
		{
			try
			{
				if (param.Length < 1)
					return commandStatus.parameters;
				String name = param[0].ToLower();
				if (!actionMap.ContainsKey(name))
				{
					outstr = String.Format(lang["cmd_unknown"], param[0]);
					return commandStatus.error;
				}
				if (name == "enablecmd")
				{
					outstr = lang["cmd_dis_enable"];
					return commandStatus.error;
				}
				if (!actionMap[name].enabled)
				{
					outstr = String.Format(lang["cmd_already_dis"], param[0]);
					return commandStatus.error;
				}
				actionMap[name].enabled = false;
				commitChanges();
				outstr = String.Format(lang["cmd_set_disable"], name);
			}
			catch (Exception e)
			{
				aIW.Log.Debug("cmd error: " + e.ToString());
			}
			return commandStatus.success;
		}

		commandStatus command_Enable(String[] param, command cmd, ref String outstr)
		{
			try
			{
				if (param.Length < 1)
					return commandStatus.parameters;
				String name = param[0].ToLower();
				if (!actionMap.ContainsKey(name))
				{
					outstr = String.Format(lang["cmd_unknown"], param[0]);
					return commandStatus.error;
				}
				if (actionMap[name].enabled)
				{
					outstr = String.Format(lang["cmd_already_ena"], param[0]);
					return commandStatus.error;
				}
				actionMap[name].enabled = true;
				commitChanges();
				outstr = String.Format(lang["cmd_set_enabled"], name);
			}
			catch (Exception e)
			{
				aIW.Log.Debug("cmd error: " + e.ToString());
			}
			return commandStatus.success;
		}

		commandStatus command_ReloadConfig(String[] param, command cmd, ref String outstr)
		{
			String type = "";
			laterActions act = laterActions.loadSettings;
			if (param.Length > 0)
				type = param[0].Trim().ToLower();
			if (type == "settings")
				act = laterActions.loadSettings;
			else
				if (type == "language")
					act = laterActions.loadLang;
				else
					if (type != "")
					{
						outstr = String.Format(lang["invalid_cfg"], type);
						return commandStatus.error;
					}
			actionQueue.Enqueue(new actionObject(act));
			actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(cmd.client, cmd.sound,
				act == laterActions.loadSettings ? lang["reloaded_cfg"] : lang["reloaded_lang"])));
			return commandStatus.success;
		}

		commandStatus command_SaveConfig(String[] param, command cmd, ref String outstr)
		{
			String type = "";
			laterActions act = laterActions.saveSettings;
			if (param.Length > 0)
				type = param[0].Trim().ToLower();
			if (type == "settings")
				act = laterActions.saveSettings;
			else
				if (type == "language")
					act = laterActions.saveLang;
				else
					if (type != "")
					{
						outstr = String.Format(lang["invalid_cfg"], type);
						return commandStatus.error;
					}
			actionQueue.Enqueue(new actionObject(act));
			actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(cmd.client, cmd.sound,
				act == laterActions.loadSettings ? lang["saved_cfg"] : lang["saved_lang"])));
			return commandStatus.success;
		}

		commandStatus command_Who(String[] param, command cmd, ref String outstr)
		{
			
			int cid;
			if (param.Length > 0)
			{
				if (parsePredicate(ref param[0]) != predicateError.ok)
				{
					outstr = lastPredicateError;
					return commandStatus.error;
				}
				if (!int.TryParse(param[0], out cid))
				{
					outstr = String.Format(lang["invalid_id"], param[0]);
					return commandStatus.error;
				}
			}
			else
				cid = cmd.client.ClientNum;
			
			AdminClient c = getClient(cid);
			if (c == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], cid);
				return commandStatus.error;
			}
			adminProfile prof = auditUser(c);
			if (prof == null)
				outstr = String.Format(lang["tell_anonymous_user"], removeColors(c.Name), -1, String.Format("{0:X}", c.GUID));
			else
				outstr = String.Format(lang["tell_admin_user"], removeColors(c.Name), prof.level, prof.ToString());
			return commandStatus.success;
		}

		public commandStatus command_Pm(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 2)
				return commandStatus.parameters;
			int cid;
			if (parsePredicate(ref param[0]) != predicateError.ok)
			{
				outstr = lastPredicateError;
				return commandStatus.error;
			}
			if (!int.TryParse(param[0], out cid))
			{
				outstr = String.Format(lang["invalid_id"], param[0]);
				return commandStatus.error;
			}
			AdminClient client = getClient(cid);
			if (client == null)
			{
				outstr = String.Format(lang["client_not_exist_id"], cid);
				return commandStatus.error;
			}
			this.echo(client, loudness.silent, String.Format(lang["pm_msg"], removeColors(cmd.client.Name), param[1].Trim()));
			this.echo(cmd.client, loudness.silent, String.Format(lang["pm_sent"], removeColors(client.Name)));
			outstr = "";
			return commandStatus.success;
		}

		public commandStatus command_Alias(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 2)
				return commandStatus.parameters;
			param[0] = param[0].ToLower();
			if (actionMap.ContainsKey(param[0]))
			{
				outstr = String.Format(lang["alias_exist_cmd"], param[0]);
				return commandStatus.error;
			}
			if (isAliasCircularRefered(param[0]))
			{
				outstr = String.Format(lang["alias_circ"], param[0]);
				return commandStatus.error;
			}
			if (aliasCmdMap.ContainsKey(param[0]))
			{
				outstr = String.Format(lang["alias_updated"], param[0]);
				aliasCmdMap[param[0]] = param[1].Trim();
			}
			else
			{
				outstr = String.Format(lang["alias_added"], param[0]);
				aliasCmdMap.Add(param[0], param[1].Trim());
			}
			commitChanges();
			return commandStatus.success;
		}

		public commandStatus command_DelAlias(String[] param, command cmd, ref String outstr)
		{
			if (param.Length < 1)
				return commandStatus.parameters;
			param[0] = param[0].ToLower();
			if (!aliasCmdMap.ContainsKey(param[0]))
			{
				outstr = String.Format(lang["alias_not_exist"], param[0]);
				return commandStatus.error;
			}
			aliasCmdMap.Remove(param[0]);
			outstr = String.Format(lang["alias_deleted"], param[0]);
			commitChanges();
			return commandStatus.success;
		}

		public commandStatus command_ClearAlias(String[] param, command cmd, ref String outstr)
		{
			if (aliasCmdMap.Count == 0)
			{
				outstr = lang["alias_empty"];
				return commandStatus.error;
			}
			aliasCmdMap.Clear();
			outstr = lang["alias_cleared"];
			commitChanges();
			return commandStatus.success;
		}

		public commandStatus command_ClearBallots(String[] param, command cmd, ref String outstr)
		{
			ballotType type = ballotType.all;
			if (param.Length > 0)
			{
				param[0] = param[0].ToLower();
				if (!ballotTypes.ContainsKey(param[0]))
				{
					outstr = String.Format(lang["invalid_ballot_type"], param[0]);
					return commandStatus.error;
				}
				type = ballotTypes[param[0]];
			}
			switch (type)
			{
				case ballotType.all:
					outstr = lang["cleared_all_ballots"];
					mapBallot.clearBallots();
					kickBallot.clearBallots();
					banBallot.clearBallots();
					break;
				case ballotType.bans:
					outstr = lang["cleared_ban_ballots"];
					banBallot.clearBallots();
					break;
				case ballotType.kicks:
					outstr = lang["cleared_kick_ballots"];
					kickBallot.clearBallots();
					break;
				case ballotType.maps:
					outstr = lang["cleared_map_ballots"];
					mapBallot.clearBallots();
					break;
				default:
					break;
			}
			return commandStatus.success;
		}

		public commandStatus command_ShutupAll(String[] param, command cmd, ref String outstr)
		{
			int level = -1;
			if (param.Length > 0)
			{
				if (!int.TryParse(param[0], out level) || level < -1)
				{
					outstr = String.Format(lang["shutup_invalid_level"], param[0]);
					return commandStatus.error;
				}
				shhLevel = level;
				outstr = String.Format(lang[shhMode ? "shutup_systematic_updated" : (shhLevel == -1 ? "shutup_systematic_on_all" : "shutup_systematic_on")], level);
				shhMode = true;
				return commandStatus.success;
			}
			shhMode = !shhMode;
			shhLevel = level;
			outstr = String.Format(lang[shhMode ? (shhLevel == -1 ? "shutup_systematic_on_all" : "shutup_systematic_on") : "shutup_systematic_off"], level);
			return commandStatus.success;
		}

		public commandStatus command_Rules(String[] param, command cmd, ref String outstr)
		{
			this.echo(cmd.client, loudness.silent, settings["server_rules"]);
			return commandStatus.success;
		}

		System.Threading.Thread thread_Exhaust = null;
		public commandStatus command_Exhaust(String[] param, command cmd, ref String outstr)
		{
			int amnt = 0;
			if (thread_Exhaust != null)
			{
				outstr = "Exhaust in progress!";
				return commandStatus.error;
			}
			if (param.Length < 1 || !int.TryParse(param[0], out amnt))
			{
				outstr = "Choose exhaust amount!";
				return commandStatus.error;
			}
			thread_Exhaust = new System.Threading.Thread(delegate(Object data)
				{
					int cnt = (int)data;
					while (cnt > 0)
					{
						actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(cmd.client, cmd.sound, "Test message!")));
						this.frame_counter = -1;
						System.Threading.Thread.Sleep(200);
						--cnt;
						while (actionQueue.Count >= 500)
							System.Threading.Thread.Sleep(200);
					}
					thread_Exhaust = null;
				});
			thread_Exhaust.Start(amnt);
			return commandStatus.success;
		}

		public void recvTransMsgCallback(command cmd, String msg)
		{
			actionQueue.Enqueue(new actionObject(laterActions.saySomething, new sayInfo(cmd.client, cmd.sound, msg)));
			this.frame_counter = -1;
		}

		public bool isAliasCircularRefered(String name)
		{
			String cmd = name;
			String[] args = null;
			while (aliasCmdMap.ContainsKey(cmd))
			{
				command.parse_cmdstr(ref cmd, ref args, aliasCmdMap[cmd], false);
				if (cmd == name || cmd == "alias")
					return true;
			}
			return false;
		}
	}
}
